/*
 *  lyf - Conway's Game of Life in C++
 *  Copyright (C) 2006-2008 Antoine Kalmbach
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ***/

#ifndef __BOARD_H
#define __BOARD_H

#include "grid.h"
#include "ruleset.h"

#include <iostream>

#include <stdexcept>
#include <string>

namespace Life {

class Board
{
private:
    Grid *grid;
    int cells_alive;
    int xs, ys;
    std::string board;
public:
    Board() { grid = 0; }
    Board(int width, int height);
    virtual ~Board() { if (grid) delete [] grid; }

    struct Stats {
        int killed, ressed, unchanged;
        Stats(int k, int r, int u) : killed(k), ressed(r), unchanged(u) {}
    };

    // update the board using the default ruleset for the game of life 
    // > iterate through every cell and check how many alive cells are next to it
    //   then depending on nearby live cell amount do the following things:
    //      2 or 3 neighbors: cell lives
    //      >3     neighbors: cell dies from overpopulation
    //      <2     neighbors: cell dies from loneliness
    //   live cells are resurrected in the new board, as we cannot work on the current board
    //   because if we change its values the procedure will go insane, cells that were alive
    //   one iteration backwards are now dead -> we need to create a copy of the grid and 
    //   destroy the old
    template<int Loneliness, int Lives, int Birth>
    Stats update(Ruleset<Loneliness, Lives, Birth> &ruleset)
    {
        // create another grid we work on
        Grid *tmpGrid = new Grid(xs, ys);
        int killed, ressed, unchanged;

        killed = ressed = unchanged = 0;
        int lives[2] = { Loneliness, Lives };

        int neighbors;
        // go through in x- and y-directions
        for (int y = 0; y < ys; y++)
        {
            for (int x = 0; x < xs; x++)
            {
                neighbors = 0;

                for (int i = -1; i <= 1; i++)
                {
                    for (int j = -1; j <= 1; j++)
                    {
                        // ignore our own status
                        if (isAlive(x+i, y+j) && !(i == 0 && j == 0)) 
                            neighbors++;
                    }
                }

                // cell is alive
                if (isAlive(x, y))
                {
                    // dies from loneliness
                    if (neighbors < Loneliness) 
                    {
                        tmpGrid->kill(x, y);
                        killed++;
                    }
                    // cell lives
                    else if (neighbors == lives[0] || neighbors == lives[1])
                    {
                        tmpGrid->ress(x, y);
                        unchanged++;
                    }
                    // dies from overpopulation
                    else
                    {
                        tmpGrid->kill(x, y);
                        killed++;
                    }
                } 
                // cell is dead, check whether we can Ress it (birth)
                else 
                {
                    if (neighbors == Birth) 
                    {
                        tmpGrid->ress(x, y);
                        ressed++;
                    }
                }
            }
        }
        // free the old grid, assign new
        delete grid;
        grid = tmpGrid;
        return Stats(killed, ressed, unchanged);
    }

    Grid& getGrid() { return *grid; }

    int width() const { return grid->width(); }
    int height() const { return grid->height(); }
    void width(int w) { xs = w; }
    void height(int h) { ys = h; }
    void print(std::ostream &os) { if (grid) grid->output(os); }
    Cell& getCell(int x, int y) { if (grid) return grid->getCell(x, y); else throw std::runtime_error("No grid"); }
    bool isAlive(int x, int y) { if (grid) return grid->isAlive(x, y); else throw std::runtime_error("No grid"); }
    void ress(int x, int y) { if (grid) grid->ress(x, y); }
    void kill(int x, int y) { if (grid) grid->kill(x, y); }
    void clear() { if (grid) grid->clear(); }
    void load(const char *filename);
};

}

#endif
